﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace AI.Graph {


    /// <summary>
    /// Use this Attribute to ignore types for ReflectionUtility operations.
    /// </summary>
    public class IgnoreReflection : Attribute {

        public IgnoreReflection () { }

    }


    public static class ReflectionUtility {

        /// <summary>
        /// Returns all types of the given base class type.
        /// Ignore classes with the Attribute ReflectionSearchIgnore.
        /// </summary>
        /// <param name="baseClass">The base class type</param>
        /// <returns>Returns an array with all types that derive from baseClass</returns>
        public static Type[] LoadClassTypes (Type baseClass) {
            List<Type> result = new List<Type> ();

            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies ()) {
                foreach (Type type in assembly.GetTypes ()) {
                    if (type.IsClass
                        && !type.IsAbstract
                        && (type == baseClass || type.IsSubclassOf (baseClass))
                        && !type.GetCustomAttributes (typeof (IgnoreReflection), false).Any ()) {
                        result.Add (type);
                    }
                }
            }

            return result.ToArray ();
        }


        /// <summary>
        /// Returns all types of the given base class type and the given attribute.
        /// Ignore classes with the Attribute ReflectionSearchIgnore.
        /// </summary>
        /// <param name="baseClass">The base class type</param>
        /// <param name="attribute">The attribute type</param>
        /// <param name="inheritAttribute">If this is set to true, inherited classes with the given attribute found too.</param>
        /// <returns>Returns an array with all types that derive from baseClass and has the given attribute</returns>
        public static Type[] LoadClassTypes (Type baseClass, Type attribute, bool inheritAttribute = false) {
            Type[] baseTypes = LoadClassTypes (baseClass);
            List<Type> result = new List<Type> ();

            foreach (Type type in baseTypes) {
                if (type.GetCustomAttributes (attribute, inheritAttribute).Any ()) {
                    result.Add (type);
                }
            }

            return result.ToArray ();
        }


        /// <summary>
        /// Returns all types of the given base class type.
        /// Ignore classes with the Attribute ReflectionSearchIgnore.
        /// </summary>
        /// <param name="TBaseClass">The base class type</param>
        /// <returns>Returns an array with all types that derive from TBaseClass</returns>
        public static Type[] LoadClassTypes<TBaseClass> () {
            return LoadClassTypes (typeof (TBaseClass));
        }


        /// <summary>
        /// Returns all types of the given base class type and the given attribute.
        /// Ignore classes with the Attribute ReflectionSearchIgnore.
        /// </summary>
        /// <param name="TBaseClass">The base class type</param>
        /// <param name="TAttribute">The attribute type</param>
        /// <param name="inheritAttribute">If this is set to true, inherited classes with the given attribute found too.</param>
        /// <returns>Returns an array with all types that derive from TBaseClass and has the given attribute</returns>
        public static Type[] LoadClassTypes<TBaseClass, TAttribute> (bool inheritAttribute = false) where TAttribute : Attribute {
            return LoadClassTypes (typeof (TBaseClass), typeof (TAttribute), inheritAttribute);
        }


        /// <summary>
        /// Returns the first found attribute of the given fromType.
        /// </summary>
        /// <param name="fromType">Get the attribute from this type.</param>
        /// <param name="attributeType">Search for this attribute.</param>
        /// <returns>Returns the first found attribute of the given fromType.</returns>
        public static Attribute GetAttribute (Type fromType, Type attributeType) {
            return Attribute.GetCustomAttribute (fromType, attributeType);
        }


        public static T GetAttribute<T> (this FieldInfo fieldInfo, bool inherit) where T : Attribute {
            return fieldInfo.GetCustomAttribute (typeof (T), inherit) as T;
        }


        public static T[] GetAttributes<T> (this Type fromType, bool inherit = true) where T : Attribute {
            return (T[]) Attribute.GetCustomAttributes (fromType, typeof (T), inherit);
        }


        /// <summary>
        /// Returns the first found attribute of the given fromType. 
        /// </summary>
        /// <param name="fromType">Get the attribute from this type.</param>
        /// <typeparam name="T">Search for this attribute.</typeparam>
        /// <returns>Returns the first found attribute of the given fromType.</returns>
        public static T GetAttribute<T> (this Type fromType, bool inherit = true) where T : Attribute {
            return (T) Attribute.GetCustomAttribute (fromType, typeof (T), inherit);
        }


        /// <summary>
        /// Returns all FieldInfos of the given classType with the given fieldType.
        /// </summary>
        /// <param name="classType">Get the FieldInfos of this class.</param>
        /// <param name="fieldType">Return only FieldInfos with this type.</param>
        /// <returns>Returns all FieldInfos of the given classType with the given fieldType.</returns>
        public static FieldInfo[] GetFields (Type classType, Type fieldType) {
            List<FieldInfo> result = new List<FieldInfo> ();

            do {
                foreach (FieldInfo field in classType.GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) {
                    if (field.FieldType == fieldType || field.FieldType.IsSubclassOf (fieldType)
                        && !field.GetCustomAttributes (typeof (IgnoreReflection), false).Any ()) {
                        result.Add (field);
                    }
                }

                classType = classType.BaseType;
            } while (classType != null);

            return result.ToArray ();
        }


        /// /// <summary>
        /// Returns all FieldInfos of the given classType with the given fieldType.
        /// </summary>
        /// <param name="classType">Get the FieldInfos of this class.</param>
        /// <param name="T">Return only FieldInfos with this type.</param>
        /// <returns>Returns all FieldInfos of the given classType with the given fieldType.</returns>
        public static FieldInfo[] GetFields<T> (this Type classType) {
            return GetFields (classType, typeof (T));
        }


        /// /// <summary>
        /// Returns all FieldInfos of the given object with the given fieldType.
        /// </summary>
        /// <param name="obj">Get the FieldInfos of this class.</param>
        /// <param name="T">Return only FieldInfos with this type.</param>
        /// <returns>Returns all FieldInfos of the given classType with the given fieldType.</returns>
        public static FieldInfo[] GetFields<T> (this object obj) {
            return GetFields (obj.GetType (), typeof (T));
        }


        /// <summary>
        /// Returns all FieldInfos of the given classType with the given fieldType.
        /// </summary>
        /// <param name="classType">Get the FieldInfos of this class.</param>
        /// <param name="fieldType">Return only FieldInfos with this type.</param>
        /// <returns>Returns all FieldInfos of the given classType with the given fieldType.</returns>
        public static FieldInfo GetField (Type classType, Type fieldType, string fieldName) {
            foreach (FieldInfo field in classType.GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) {
                if (field.FieldType == fieldType || field.FieldType.IsSubclassOf (fieldType)
                    && !field.GetCustomAttributes (typeof (IgnoreReflection), false).Any ()
                    && field.Name == fieldName) {
                    return field;
                }
            }

            return null;
        }

    }


}